/** @file f_absfs.cpp
	@brief Abstract filesystem */

#include "file/f_absfs.h"

using namespace G3;

G3::ABSFS G3::MainFilesys;

//==============================================================================
// Constructors
//==============================================================================
G3::ABS_FILE_ENTRY::ABS_FILE_ENTRY (const std::string &Path1, const std::string &Path2) {
	DirPath = Path1;
	FilePath = Path2;
}

G3::ABS_REAL_STRUCT::ABS_REAL_STRUCT () {
	Pointer = NULL;
	Parent = NULL;
}

G3::ABS_LOCAL_PATH::ABS_LOCAL_PATH (const char *Local, G3::LOG *pLog) {
	if (Local)
		LocalPath = TrimDelimiters (std::string (Local), ABSFS_DIR_DELIMS);

	Log = pLog;
}

G3::ABS_LOCAL_STRUCT::ABS_LOCAL_STRUCT () {
	Pointer = NULL;
}

G3::ABSFS::ABSFS () {
	Log = &G3::MainLog;

	// Add the root dir - with no abstraction
	ABS_LOCAL_STRUCT ls = AddLocal ("");
	// And if that proved to be successful, then add a fresh Real Path into it
	if (ls.Pointer) {
		ABS_REAL_STRUCT	rs = ls.Pointer->AddReal ("");
		// Successful?
		if (!rs.Pointer && Log) {
			Log->Report ("ERROR! ABSFS::MountDir: Failed to mount the root dir (Error while adding a RealPath)..\n");
		} else {
			// Add the RealPath into the backward links list then
			BackwardLinks [""] = rs;
		}
	}
}

//==============================================================================
// = operators
//==============================================================================
ABS_FILE_ENTRY& G3::ABS_FILE_ENTRY::operator=(const ABS_FILE_ENTRY &f) {
	DirPath = f.DirPath;
	FilePath = f.FilePath;

	return *this;
}

ABS_REAL_STRUCT& G3::ABS_REAL_STRUCT::operator=(const ABS_REAL_STRUCT &rs) {
	Iter = rs.Iter;
	Pointer = rs.Pointer;
	Parent = rs.Parent;

	return *this;
}

ABS_LOCAL_PATH& G3::ABS_LOCAL_PATH::operator=(const ABS_LOCAL_PATH &lp) {
	LocalPath = lp.LocalPath;
	RealPaths = lp.RealPaths;
	Log = lp.Log;

	return *this;
}

ABS_LOCAL_STRUCT& G3::ABS_LOCAL_STRUCT::operator=(const ABS_LOCAL_STRUCT &lc) {
	Iter = lc.Iter;
	Pointer = lc.Pointer;

	return *this;
}

//==============================================================================
// < operators
//==============================================================================
bool G3::ABS_LOCAL_PATH::operator<(const ABS_LOCAL_PATH &lp) const {
	return (LocalPath < lp.LocalPath);
}

//==============================================================================
// == operators
//==============================================================================
bool G3::ABS_LOCAL_PATH::operator==(const ABS_LOCAL_PATH &lp) const {
	return (LocalPath == lp.LocalPath);
}

//==============================================================================
// Gets the whole path of the file
//==============================================================================
std::string ABS_FILE_ENTRY::GetWholePath () {
	return DirPath + FilePath;
}

//==============================================================================
// Finds a real path from the set
//==============================================================================
ABS_REAL_STRUCT G3::ABS_LOCAL_PATH::FindReal (const char *RealPath) {
	ABS_REAL_STRUCT rs;
	rs.Iter = RealPaths.end ();
	rs.Pointer = NULL;
	rs.Parent = this;

	Assert (RealPath, "ABS_LOCAL_PATH::FindReal: The RealPath argument is a NULL pointer");

	string TrimmedReal = TrimDelimiters (std::string (RealPath), ABSFS_DIR_DELIMS);

	rs.Iter = RealPaths.find (TrimmedReal);

	if (rs.Iter != RealPaths.end ())
		rs.Pointer = (std::string *) (&(*rs.Iter));

	return rs;
}

//==============================================================================
// Inserts a real path into the set
//==============================================================================
ABS_REAL_STRUCT G3::ABS_LOCAL_PATH::AddReal (const char *RealPath) {
	ABS_REAL_STRUCT rs;
	rs.Iter = RealPaths.end ();
	rs.Pointer = NULL;
	rs.Parent = this;

	Assert (RealPath, "ABS_LOCAL_PATH::AddReal: The RealPath argument is a NULL pointer");

	std::string TrimmedReal = TrimDelimiters (std::string (RealPath), ABSFS_DIR_DELIMS);

	std::pair<std::set<std::string>::iterator, bool> pRet;

	pRet = RealPaths.insert (TrimmedReal);

	rs.Iter = pRet.first;
	rs.Pointer = (std::string *) (&(*pRet.first));

	return rs;
}

//==============================================================================
// Finds a local path from the set
//==============================================================================
ABS_LOCAL_STRUCT G3::ABSFS::FindLocal (const char *Local) {
	ABS_LOCAL_STRUCT ls;
	ls.Iter = LocalPaths.end ();
	ls.Pointer = NULL;

	Assert (Local, "ABSFS::FindLocal: The Local argument is a NULL pointer");

	ls.Iter = LocalPaths.find (ABS_LOCAL_PATH (Local));

	if (ls.Iter != LocalPaths.end ())
		ls.Pointer = (ABS_LOCAL_PATH *) (&(*ls.Iter));

	return ls;
}

//==============================================================================
// Adds a local path into the set
//==============================================================================
ABS_LOCAL_STRUCT G3::ABSFS::AddLocal (const char *Local) {
	ABS_LOCAL_STRUCT ls;
	ls.Iter = LocalPaths.end ();
	ls.Pointer = NULL;

	Assert (Local, "ABSFS::AddLocal: The Local argument is a NULL pointer");

	std::pair<std::set<ABS_LOCAL_PATH>::iterator, bool> pRet;

	pRet = LocalPaths.insert (ABS_LOCAL_PATH (Local, Log));

	ls.Iter = pRet.first;
	ls.Pointer = (ABS_LOCAL_PATH *) (&(*pRet.first));

	return ls;
}

//==============================================================================
// Mounts a real path as a local path
//==============================================================================
bool G3::ABSFS::MountDir (const char *Path, const char *Local) {
	Assert (Path, "ABSFS::MountDir: The Path argument is a NULL pointer");
	Assert (Local, "ABSFS::MountDir: The Local argument is a NULL pointer");

	// Search for a mounted directory with the same Local and Real name
	ABS_LOCAL_STRUCT ls = FindLocal (Local);
	ABS_REAL_STRUCT rs;

	if (ls.Pointer)
		rs = ls.Pointer->FindReal (Path);

	if (ls.Pointer && rs.Pointer) {
		if (Log)
			Log->Report ("WARNING! ABSFS::MountDir: Directory is \"%s\" already mounted..\n", Path);
		return false;

	} else {
		// Good, this directory is not mounted yet..
		ABS_REAL_STRUCT rs2;

		// There already is a local directory with this name? Only add the Real Path then.
		if (ls.Pointer && !rs.Pointer) {
			rs2 = ls.Pointer->AddReal (Path);
			// Successful?
			if (!rs2.Pointer && Log) {
				Log->Report ("ERROR! ABSFS::MountDir: Failed to mount (Error while adding a RealPath)..\n");
				return false;
			}
			// Add the RealPath into the backward links list then
			BackwardLinks [TrimDelimiters (std::string (Path), ABSFS_DIR_DELIMS)] = rs2;

		// Alright, we also need to add a Local Path..
		} else if (!ls.Pointer) {
			ABS_LOCAL_STRUCT ls2 = AddLocal (Local);
			// And if that proved to be successful, then add a fresh Real Path into it
			if (ls2.Pointer) {
				rs2 = ls2.Pointer->AddReal (Path);
				// Successful?
				if (!rs2.Pointer && Log) {
					Log->Report ("ERROR! ABSFS::MountDir: Failed to mount (Error while adding a RealPath)..\n");
					return false;
				}
				// Add the RealPath into the backward links list then
				BackwardLinks [TrimDelimiters (std::string (Path), ABSFS_DIR_DELIMS)] = rs2;

			// If not successful, then report of this error
			} else {
				if (Log) {
					Log->Report ("ERROR! ABSFS::MountDir: Failed to mount (Error while adding a LocalPath)..\n");
					return false;
				}
			}
		}
	}
	return true;
}

//==============================================================================
// Unmounts a real path
//==============================================================================
bool G3::ABSFS::UnMountDir (const char *Path) {
	Assert (Path, "ABSFS::UnMountDir: The Path argument is a NULL pointer");

	// Search for that Path
	std::string sPath = std::string (Path);
	std::map<std::string, ABS_REAL_STRUCT>::iterator it;
	it = BackwardLinks.find (sPath);

	// Found it?
	if (it != BackwardLinks.end ()) {
		// Just in case
		Assert (it->second.Pointer && it->second.Parent, "ABSFS::UnMountDir: Invalid backward link");

		// Erase this RealPath
		it->second.Parent->RealPaths.erase (it->second.Iter);
		// This was the only RealPath? Then erase the LocalPath too
		if (it->second.Parent->RealPaths.empty ())
			LocalPaths.erase (*it->second.Parent);

		// And this backward link aswell
		BackwardLinks.erase (it);
	} else {
		if (Log)
			Log->Report ("WARNING! ABSFS::UnMountDir: The Directory \"%s\" is not mounted..\n", Path);
		return false;
	}

	// We should also clear the cache of any related entries
	std::map<std::string, ABS_FILE_ENTRY>::iterator itf = CachedFilePaths.begin ();
	std::map<std::string, ABS_FILE_ENTRY>::iterator itfp = itf;
	while (itf != CachedFilePaths.end ()) {
		if (TrimDelimiters (itf->second.DirPath, ABSFS_DIR_DELIMS) == sPath) {
			// Save the next iterator
			itfp = itf; itfp++;
			// itf is cleared aswell
			CachedFilePaths.erase (itf);
			// And restore it
			itf = itfp;
		}

		itf++;
	}

	return true;
}

//==============================================================================
// Unmounts all
//==============================================================================
void G3::ABSFS::UnMountAll () {
	LocalPaths.clear ();
	BackwardLinks.clear ();
	CachedFilePaths.clear ();
}

//==============================================================================
// Gets the real path from the local path
//==============================================================================
string G3::ABSFS::GetPath (const char *LocalFile) {
	Assert (LocalFile, "ABSFS::GetPath: The LocalFile argument is a NULL pointer");

	ABS_LOCAL_STRUCT ls;
	std::set<std::string>::iterator it;
	std::string local, str, str2;
	size_t pos = string::npos;

	local = std::string (LocalFile);

	// If the path is already cached, there is no need for searching through
	// all those directories
	std::map<std::string, ABS_FILE_ENTRY>::iterator itf = CachedFilePaths.find (local);
	if (itf != CachedFilePaths.end ()) {
		if (MainFileHandler.FileExists (itf->second.GetWholePath ().c_str ()))
			return itf->second.GetWholePath ();
	}

	// Start from the last character
	pos = local.size () - 1;

	while (pos >= 0) {
		// Finds a slash that might give us the local directory that LocalPaths set contains
		pos = local.find_last_of (G3_SLASH, pos);
		str = local.substr (0, pos);

		// If no slashes / other delimiters found, there is no local path - root directory only
		if (pos == std::string::npos)
			str = "";

		// Searches for a local path that this LocalFile contains
		ls = FindLocal (str.c_str ());
		// If found, then..
		if (ls.Pointer) {
			// .. search for the real directory that contains the asked file
			it = ls.Pointer->RealPaths.begin ();
			while (it != ls.Pointer->RealPaths.end ()) {
				// Makes sure that we don't string::substr (string::npos, string::npos)
				// And then just adds together current RealPath, a slash and the
				// local filename
				str2 = TrimDelimiters ((*it) + G3_SLASH, "\t ");

				if (pos == std::string::npos)
					str = str2 + local;
				else
					str = str2 + local.substr (pos + 1, std::string::npos);

				// Trim it - just in case
				str = TrimDelimiters (str, ABSFS_DIR_DELIMS);

				// If the file exists, it must be what we were looking for
				if (MainFileHandler.FileExists (str.c_str ())) {
					// Cache this file-entry
					CachedFilePaths.insert (std::pair<std::string, ABS_FILE_ENTRY> (local, ABS_FILE_ENTRY (str2, local)));
					// And return the path
					return str;
				}

				// Take the next RealPath
				it++;
			}
			break;
		}
		// Move the offset by 1 character in order to make sure that this loop is not infinite
		pos--;
	}

	return "";
}

//==============================================================================
// Checks if a file with an abstract path exists
//==============================================================================
bool G3::ABSFS::FileExists (const std::string &LocalFile) {
    if (LocalFile.empty ())
        return false;
    return (!GetPath (LocalFile.c_str ()).empty ());
}

//==============================================================================
// Dumps the filesystem into the Log
//==============================================================================
void G3::ABSFS::Dump () {
	if (Log) {
		std::set<ABS_LOCAL_PATH>::iterator itl;
		std::set<std::string>::iterator itr;
		std::map<std::string, ABS_REAL_STRUCT>::iterator itb;
		std::map<std::string, ABS_FILE_ENTRY>::iterator itf;

		Log->Report ("Dumping LocalPaths:\n");
		if (!LocalPaths.empty ()) {
			itl = LocalPaths.begin ();
			while (itl != LocalPaths.end ()) {
				Log->Report (" LocalPath: \"%s\"\n", itl->LocalPath.c_str ());
				Log->Report ("  RealPaths:\n");

				itr = itl->RealPaths.begin ();
				while (itr != itl->RealPaths.end ()) {
					Log->Report ("   Path: \"%s\"\n", itr->c_str ());

					itr++;
				}

				itl++;
			}
		} else {
			Log->Report (" <Empty>\n");
		}

		Log->Report ("\nDumping BackwardLinks:\n");
		if (!BackwardLinks.empty ()) {
			itb = BackwardLinks.begin ();
			while (itb != BackwardLinks.end ()) {
				if (itb->second.Pointer)
					Log->Report (" Link: \"%s\" -> RealPath: \"%s\"\n", itb->first.c_str (), itb->second.Pointer->c_str ());
				else
					Log->Report (" Link: \"%s\" -> RealPath: <Invalid>\n", itb->first.c_str ());

				itb++;
			}
		} else {
			Log->Report (" <Empty>\n");
		}

		Log->Report ("\nDumping Cached FilePaths:\n");
		if (!CachedFilePaths.empty ()) {
			itf = CachedFilePaths.begin ();
			while (itf != CachedFilePaths.end ()) {
				Log->Report (" Dir: \"%s\" -> \"%s\" + \"%s\"\n", itf->first.c_str (), itf->second.DirPath.c_str (), itf->second.FilePath.c_str ());

				itf++;
			}
		} else {
			Log->Report (" <Empty>\n");
		}
	}
}
